/**
 * Creates new prefetcher
 *
 * @param configuration Comfiguration to use
 * @returns Builder interface
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function createPrefetcher(configuration) {
    return new PrefetcherBuilder(configuration);
}
/**
 * Link prefetcher solution with the code running in worker thread
 *
 * @param workerPort
 * @param logger
 * @returns Prefetcher solution linker
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function linkPrefetcher(prefetcherId, logger) {
    return new WorkerLinker(prefetcherId, logger);
}
const workerPortProxies = [];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class WorkerLinker {
    constructor(prefetcherId, logger) {
        this.prefetcherId = prefetcherId;
        this.logger = logger;
    }
    toPort(workerPort) {
        return new WithPortWorkerLinker(workerPort, this.prefetcherId, this.logger);
    }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class WithPortWorkerLinker {
    constructor(workerPort, prefetcherId, logger) {
        this.workerPort = workerPort;
        this.prefetcherId = prefetcherId;
        this.logger = logger;
    }
    withAgent(fetchAgent) {
        const w = new WorkerPortProxy(this.prefetcherId, this.workerPort, fetchAgent, this.logger);
        workerPortProxies.push(w);
    }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class PrefetcherBuilder {
    constructor(configuration) {
        this.configuration = configuration;
    }
    withDataSource(dataSource) {
        return new LoadMorePrefetcherBuilder(undefined, dataSource, this.configuration);
    }
}
class LoadMorePrefetcherBuilder {
    constructor(callback, dataSource, configuration) {
        this.callback = callback;
        this.dataSource = dataSource;
        this.configuration = configuration;
    }
    onLoadMore(callback) {
        return new LoadMorePrefetcherBuilder(callback, this.dataSource, this.configuration);
    }
    onMainThread() {
        return new MainThreadPrefetcherBuilder(this.callback, this.dataSource, this.configuration);
    }
    onWorkerThread(workerThread) {
        return new WorkerThreadPrefetcherBuilder(workerThread, this.callback, this.dataSource, this.configuration);
    }
}
class WorkerThreadPrefetcherBuilder {
    constructor(workerThread, callback, dataSource, configuration) {
        this.workerThread = workerThread;
        this.callback = callback;
        this.dataSource = dataSource;
        this.configuration = configuration;
    }
    withId(prefetcherId) {
        if (!workerThreadFacades.has(this.workerThread)) {
            workerThreadFacades.set(this.workerThread, new WorkerThreadProxy(this.workerThread, this.configuration?.logger));
        }
        const broker = new WorkerThreadFetchAgentBroker(prefetcherId, this.dataSource, workerThreadFacades.get(this.workerThread), this.configuration?.logger);
        return new BrokerBasedPrefetcher(this.callback, broker, this.configuration);
    }
}
class MainThreadPrefetcherBuilder {
    constructor(callback, dataSource, configuration) {
        this.callback = callback;
        this.dataSource = dataSource;
        this.configuration = configuration;
    }
    withAgent(fetchAgent) {
        const broker = new MainThreadFetchAgentBroker(this.dataSource, fetchAgent, this.configuration?.logger);
        return new BrokerBasedPrefetcher(this.callback, broker, this.configuration);
    }
}
const workerThreadFacades = new Map();
//----------- Prefetcher builder ---
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Fetch agent has to reject fetch promise by this error in case related operation was canceled and ended with error
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class CanceledError extends Error {
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class BrokerBasedPrefetcher {
    constructor(loadMoreCallback, broker, configuration) {
        const logger = configuration?.logger ?? dummyLogger;
        logger.info('BrokerBasedPrefetcher constructor');
        const itemsOnScreen = new ItemsOnScreenProvider();
        const prefetchCount = new PrefetchCount(itemsOnScreen, logger);
        const fetchedRegistry = new FetchedRegistry();
        const prefetchRangeRatio = new PrefetchRangeRatio(itemsOnScreen, fetchedRegistry);
        this.fetchingDriver = new FetchingDriver(broker, new FetchingRegistry(), new RangeToFetchProvider(itemsOnScreen, prefetchCount, prefetchRangeRatio, fetchedRegistry, logger), new DefaultTimeProvider(), logger, configuration?.autoStart === undefined || configuration?.autoStart, loadMoreCallback);
    }
    visibleAreaChanged(minVisible, maxVisible) {
        this.fetchingDriver.visibleAreaChanged(minVisible, maxVisible);
    }
    stop() {
        this.fetchingDriver.stop();
    }
    start() {
        this.fetchingDriver.start();
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
class DataSourceBroker {
    constructor(dataSource) {
        this.dataSource = dataSource;
    }
    fetch(index) {
        return this.dataSource?.prefetch(index);
    }
    cancel(index) {
        if (this.dataSource?.cancel) {
            return this.dataSource?.cancel(index);
        }
    }
    setDataSource(ds) {
        this.dataSource = ds;
    }
    start() { }
    stop() { }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class BasicPrefetcher {
    constructor(ds, configuration) {
        const logger = configuration?.logger ?? dummyLogger;
        logger.info('BasicPrefetcher constructor');
        const itemsOnScreen = new ItemsOnScreenProvider();
        const prefetchCount = new PrefetchCount(itemsOnScreen, logger);
        const fetchedRegistry = new FetchedRegistry();
        const prefetchRangeRatio = new PrefetchRangeRatio(itemsOnScreen, fetchedRegistry);
        const broker = new DataSourceBroker(ds ?? dummyDataSource);
        this.fetchingDriver = new FetchingDriver(broker, new FetchingRegistry(), new RangeToFetchProvider(itemsOnScreen, prefetchCount, prefetchRangeRatio, fetchedRegistry), new DefaultTimeProvider(), logger, configuration?.autoStart === undefined || configuration?.autoStart);
    }
    setDataSource(ds) {
        this.fetchingDriver.setDataSource(ds);
    }
    visibleAreaChanged(minVisible, maxVisible) {
        this.fetchingDriver.visibleAreaChanged(minVisible, maxVisible);
    }
    stop() {
        this.fetchingDriver.stop();
    }
    start() {
        this.fetchingDriver.start();
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class DataSourceObserver {
    constructor(simpleChangeListener) {
        this.simpleChangeListener = simpleChangeListener;
        this.MyMegaUnique4 = 81271637;
    }
    onDataReloaded() {
        this.simpleChangeListener.batchUpdate([
            {
                kind: 'reloaded',
                totalCount: this.dataSource?.totalCount() ?? 0,
            },
        ]);
    }
    onDataAdded(index) {
        this.simpleChangeListener.batchUpdate([
            {
                kind: 'added',
                startIndex: index,
                count: 1,
            },
        ]);
    }
    onDataAdd(index) {
        this.onDataAdded(index);
    }
    onDataMoved(from, to) {
        this.simpleChangeListener.batchUpdate([
            {
                kind: 'swapped',
                a: from,
                b: to,
            },
        ]);
    }
    onDataMove(from, to) {
        this.onDataMoved(from, to);
    }
    onDataDeleted(index) {
        this.simpleChangeListener.batchUpdate([
            {
                kind: 'deleted',
                startIndex: index,
                count: 1,
            },
        ]);
    }
    onDataDelete(index) {
        this.onDataDeleted(index);
    }
    onDataChanged(index) {
        this.simpleChangeListener.batchUpdate([
            {
                kind: 'updated',
                index,
            },
        ]);
    }
    onDataChange(index) {
        this.onDataChanged(index);
    }
    onDatasetChange(dataOperations) {
        console.log('onDatasetChange in listener ' + this.MyMegaUnique4);
        const operations = [];
        dataOperations.forEach((operation) => {
            switch (operation.type) {
                case 'delete':
                    if (operation.count === undefined || operation.count > 0) {
                        operations.push({
                            kind: 'deleted',
                            startIndex: operation.index,
                            count: operation.count ?? 1,
                        });
                    }
                    break;
                case 'add':
                    if (operation.count === undefined || operation.count > 0) {
                        operations.push({
                            kind: 'added',
                            startIndex: operation.index,
                            count: operation.count ?? 1,
                        });
                    }
                    break;
                case 'change':
                    operations.push({
                        kind: 'updated',
                        index: operation.index,
                    });
                    break;
                case 'reload':
                    operations.push({
                        kind: 'reloaded',
                        totalCount: this.dataSource?.totalCount() ?? 0,
                    });
                    break;
                case 'exchange':
                    operations.push({
                        kind: 'swapped',
                        a: operation.index.start,
                        b: operation.index.end,
                    });
                    break;
                case 'move':
                    operations.push({
                        kind: 'moved',
                        from: operation.index.from,
                        to: operation.index.to,
                    });
                    break;
                default:
                    // istanbul ignore next
                    assertNever(operation);
            }
        });
        this.simpleChangeListener.batchUpdate(operations);
    }
    setDataSource(dataSource) {
        if (this.dataSource) {
            this.dataSource.unregisterDataChangeListener(this);
        }
        this.dataSource = dataSource;
        this.dataSource.registerDataChangeListener(this);
        this.onDataReloaded();
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FetchingRegistry {
    constructor() {
        this.fetches = new Map();
        this.fetching = new Map();
        this.fetchCounter = 0;
    }
    registerFetch(index) {
        let fetchId = this.fetching.get(index);
        if (fetchId !== undefined) {
            return fetchId;
        }
        fetchId = ++this.fetchCounter;
        this.fetching.set(index, fetchId);
        this.fetches.set(fetchId, index);
        return fetchId;
    }
    getItem(fetchId) {
        return this.fetches.get(fetchId);
    }
    deleteFetch(fetchId) {
        const index = this.fetches.get(fetchId);
        if (index !== undefined) {
            this.fetching.delete(index);
            this.fetches.delete(fetchId);
        }
    }
    deleteFetchByItem(index) {
        const fetchId = this.fetching.get(index);
        if (fetchId !== undefined) {
            this.fetching.delete(index);
            this.fetches.delete(fetchId);
        }
    }
    isFetchingItem(index) {
        return this.fetching.has(index);
    }
    incrementAllIndexesGreaterThen(value) {
        this.offsetAllIndexesGreaterThen(value, 1);
    }
    getAllIndexes() {
        const set = new Set();
        this.fetching.forEach((fetchId, itemIndex) => set.add(itemIndex));
        return set;
    }
    offsetAllIndexesGreaterThen(value, offset) {
        const newFetching = new Map();
        this.fetches.forEach((index, fetchId) => {
            const toSet = index > value ? index + offset : index;
            newFetching.set(toSet, fetchId);
            this.fetches.set(fetchId, toSet);
        });
        this.fetching = newFetching;
    }
    decrementAllIndexesGreaterThen(value) {
        this.offsetAllIndexesGreaterThen(value, -1);
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FetchedRegistry {
    constructor() {
        this.fetchedIndexes = new Set(); // {I where I is in cache} ∩ `prefetchRange`
        this.rangeToFetch_ = new IndexRange(0, 0);
        this.missedIndexes = new Set();
    }
    get rangeToFetch() {
        return this.rangeToFetch_;
    }
    addFetched(index) {
        if (this.rangeToFetch_.contains(index)) {
            this.fetchedIndexes.add(index);
            this.missedIndexes.delete(index);
        }
    }
    removeFetched(index) {
        if (this.rangeToFetch_.contains(index)) {
            this.fetchedIndexes.delete(index);
            this.missedIndexes.add(index);
        }
    }
    has(index) {
        return this.fetchedIndexes.has(index);
    }
    getFetchedInRange(range) {
        let fetched = 0;
        range.forEachIndex((index) => {
            fetched += this.fetchedIndexes.has(index) ? 1 : 0;
        });
        return fetched;
    }
    updateRangeToFetch(fetchRange) {
        this.rangeToFetch_.subtract(fetchRange).forEachIndex((index) => {
            this.fetchedIndexes.delete(index);
        });
        this.rangeToFetch_ = fetchRange;
        this.missedIndexes.clear();
        this.rangeToFetch_.forEachIndex((index) => {
            if (!this.fetchedIndexes.has(index)) {
                this.missedIndexes.add(index);
            }
        });
    }
    getItemsToFetch() {
        return new Set(this.missedIndexes);
    }
    incrementFetchedGreaterThen(value, newFetchRange) {
        this.offsetAllGreaterThen(value, 1);
        this.updateRangeToFetch(newFetchRange);
    }
    decrementFetchedGreaterThen(value, newFetchRange) {
        this.offsetAllGreaterThen(value, -1);
        this.updateRangeToFetch(newFetchRange);
    }
    offsetAllGreaterThen(value, offset) {
        const updated = new Set();
        this.fetchedIndexes.forEach((index) => {
            updated.add(index > value ? index + offset : index);
        });
        this.fetchedIndexes = updated;
    }
    clearFetched(newFetchRange) {
        this.fetchedIndexes.clear();
        this.updateRangeToFetch(newFetchRange);
    }
    toString() {
        const missed = [];
        missed.push(...this.missedIndexes.values());
        return `r2f: ${JSON.stringify(this.rangeToFetch_)} missed: ${JSON.stringify(missed)}`;
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class ItemsOnScreenProvider {
    constructor() {
        this.firstScreen = true;
        this.meanImagesOnScreen = 0;
        this.minVisible = 0;
        this.maxVisible = 0;
        this._direction = 'UNKNOWN';
        this._visibleRange = new IndexRange(0, 0);
        this.callbacks = [];
    }
    register(callback) {
        this.callbacks.push(callback);
    }
    get visibleRange() {
        return this._visibleRange;
    }
    get meanValue() {
        return this.meanImagesOnScreen;
    }
    get direction() {
        return this._direction;
    }
    update(minVisible, maxVisible) {
        if (minVisible !== this.minVisible || maxVisible !== this.maxVisible) {
            if (Math.max(minVisible, this.minVisible) === minVisible &&
                Math.max(maxVisible, this.maxVisible) === maxVisible) {
                this._direction = 'DOWN';
            }
            else if (Math.min(minVisible, this.minVisible) === minVisible &&
                Math.min(maxVisible, this.maxVisible) === maxVisible) {
                this._direction = 'UP';
            }
        }
        this.minVisible = minVisible;
        this.maxVisible = maxVisible;
        let imagesOnScreen = maxVisible - minVisible + 1;
        if (this.firstScreen) {
            this.meanImagesOnScreen = imagesOnScreen;
            this.firstScreen = false;
        }
        else {
            const weight = 0.95;
            this.meanImagesOnScreen = this.meanImagesOnScreen * weight + (1 - weight) * imagesOnScreen;
            imagesOnScreen = Math.ceil(this.meanImagesOnScreen);
        }
        const visibleRangeSizeChanged = this.visibleRange.length !== imagesOnScreen;
        this._visibleRange = new IndexRange(minVisible, maxVisible + 1);
        if (visibleRangeSizeChanged) {
            this.notifyObservers();
        }
    }
    notifyObservers() {
        this.callbacks.forEach((callback) => callback());
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class PrefetchCount {
    constructor(itemsOnScreen, logger = dummyLogger) {
        this.itemsOnScreen = itemsOnScreen;
        this.logger = logger;
        this.MAX_SCREENS = 4;
        this.MIN_SCREENS = 0.6;
        this.min = 0;
        this.max = 0;
        this._prefetchCountValue = 0;
        this._currentLimit = 0;
        this._maxRatio = 0.5;
        this.itemsOnScreen = itemsOnScreen;
        this.itemsOnScreen.register(() => {
            this.updateLimits();
        });
    }
    get prefetchCountValue() {
        return this._prefetchCountValue;
    }
    set prefetchCountValue(v) {
        this._prefetchCountValue = v;
        this.logger.debug(`{"tm":${Date.now()},"prefetch_count":${v}}`);
    }
    get currentLimit() {
        return this._currentLimit;
    }
    get maxRatio() {
        return this._maxRatio;
    }
    set maxRatio(value) {
        this._maxRatio = value;
        this.updateCurrentLimit();
    }
    getPrefetchCountByRatio(ratio) {
        return this.min + Math.ceil(ratio * (this.currentLimit - this.min));
    }
    getRangeToFetch(totalCount) {
        const visibleRange = this.itemsOnScreen.visibleRange;
        let start = 0;
        let end = 0;
        switch (this.itemsOnScreen.direction) {
            case 'UNKNOWN':
                start = Math.max(0, visibleRange.start - Math.round(this.prefetchCountValue));
                end = Math.min(totalCount, visibleRange.end + Math.round(this.prefetchCountValue));
                break;
            case 'UP':
                start = Math.max(0, visibleRange.start - Math.round(this.prefetchCountValue));
                end = Math.min(totalCount, visibleRange.end + Math.round(this.prefetchCountValue * 0.5));
                break;
            case 'DOWN':
                start = Math.max(0, visibleRange.start - Math.round(this.prefetchCountValue * 0.5));
                end = Math.min(totalCount, visibleRange.end + Math.round(this.prefetchCountValue));
                break;
        }
        if (start > end) {
            start = end;
        }
        return new IndexRange(start, end);
    }
    updateLimits() {
        this.min = Math.ceil(this.itemsOnScreen.meanValue * this.MIN_SCREENS);
        this.max = Math.max(this.min, Math.ceil(this.MAX_SCREENS * this.itemsOnScreen.meanValue));
        this.updateCurrentLimit();
    }
    updateCurrentLimit() {
        this._currentLimit = Math.max(this.min, Math.ceil(this.max * this._maxRatio));
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class RangeToFetchProvider {
    constructor(itemsOnScreen, prefetchCount, prefetchRangeRatio, fetchedRegistry, logger = dummyLogger) {
        this.itemsOnScreen = itemsOnScreen;
        this.prefetchCount = prefetchCount;
        this.prefetchRangeRatio = prefetchRangeRatio;
        this.fetchedRegistry = fetchedRegistry;
        this.logger = logger;
        this.totalItems = 0;
    }
    get rangeToFetch() {
        return this.fetchedRegistry.rangeToFetch;
    }
    getItemsToFetch() {
        return this.fetchedRegistry.getItemsToFetch();
    }
    isItemFetched(index) {
        return this.fetchedRegistry.has(index);
    }
    updateRangeToFetch(whatHappened) {
        switch (whatHappened.kind) {
            case 'visible-area-changed':
                this.onVisibleAreaChange(whatHappened.minVisible, whatHappened.maxVisible);
                break;
            case 'item-fetched':
                this.onItemFetched(whatHappened.itemIndex, whatHappened.fetchDuration);
                break;
            case 'collection-changed':
                this.onCollectionChanged(whatHappened.totalCount);
                break;
            case 'item-added':
                this.onItemAdded(whatHappened.itemIndex);
                break;
            case 'item-removed':
                this.onItemDeleted(whatHappened.itemIndex);
                break;
            case 'items-swapped':
                this.onItemsSwapped(whatHappened.indexA, whatHappened.indexB);
                break;
            case 'item-moved':
                this.onItemMoved(whatHappened.from, whatHappened.to);
                break;
            case 'item-updated':
                this.itemUpdated(whatHappened.itemIndex);
                break;
            default:
                // istanbul ignore next
                assertNever(whatHappened);
        }
    }
    onVisibleAreaChange(minVisible, maxVisible) {
        this.itemsOnScreen.update(minVisible, maxVisible);
        this.logger.debug(`visibleAreaChanged itemsOnScreen=${Math.abs(maxVisible - minVisible) + 1}, meanImagesOnScreen=${this.itemsOnScreen.meanValue}, prefetchCountCurrentLimit=${this.prefetchCount.currentLimit}, prefetchCountMaxRatio=${this.prefetchCount.maxRatio}`);
        this.prefetchCount.prefetchCountValue = this.evaluatePrefetchCount('visible-area-changed');
        const rangeToFetch = this.prefetchCount.getRangeToFetch(this.totalItems);
        this.fetchedRegistry.updateRangeToFetch(rangeToFetch);
    }
    onItemFetched(index, fetchDuration) {
        if (!this.fetchedRegistry.rangeToFetch.contains(index))
            return;
        let maxRatioChanged = false;
        if (this.prefetchRangeRatio.update(fetchDuration) === 'ratio-changed') {
            this.prefetchCount.maxRatio = this.prefetchRangeRatio.maxRatio;
            maxRatioChanged = true;
            this.logger.debug(`choosePrefetchCountLimit prefetchCountMaxRatio=${this.prefetchCount.maxRatio}, prefetchCountCurrentLimit=${this.prefetchCount.currentLimit}`);
        }
        this.fetchedRegistry.addFetched(index);
        this.prefetchCount.prefetchCountValue = this.evaluatePrefetchCount('resolved', maxRatioChanged);
        const rangeToFetch = this.prefetchCount.getRangeToFetch(this.totalItems);
        this.fetchedRegistry.updateRangeToFetch(rangeToFetch);
    }
    evaluatePrefetchCount(event, maxRatioChanged) {
        const ratio = this.prefetchRangeRatio.calculateRatio(this.prefetchCount.prefetchCountValue, this.totalItems);
        let evaluatedPrefetchCount = this.prefetchCount.getPrefetchCountByRatio(ratio);
        let nextRatio;
        if (maxRatioChanged) {
            nextRatio = this.prefetchRangeRatio.calculateRatio(evaluatedPrefetchCount, this.totalItems);
            evaluatedPrefetchCount = this.prefetchCount.getPrefetchCountByRatio(nextRatio);
        }
        if (this.prefetchRangeRatio.range.contains(ratio) ||
            (event === 'resolved' && evaluatedPrefetchCount <= this.prefetchCount.prefetchCountValue)) {
            return this.prefetchCount.prefetchCountValue;
        }
        this.prefetchRangeRatio.updateRatioRange(ratio);
        this.logger.debug(`evaluatePrefetchCount prefetchCount=${evaluatedPrefetchCount}, ratio=${ratio}, nextRatio=${nextRatio}, hysteresisRange=${this.prefetchRangeRatio.range}`);
        return evaluatedPrefetchCount;
    }
    onCollectionChanged(totalCount) {
        if (totalCount < 0) {
            totalCount = 0;
        }
        this.totalItems = totalCount;
        let newRangeToFetch;
        if (this.fetchedRegistry.rangeToFetch.length > 0) {
            newRangeToFetch = this.itemsOnScreen.visibleRange;
        }
        else {
            newRangeToFetch = this.fetchedRegistry.rangeToFetch;
        }
        if (newRangeToFetch.end > this.totalItems) {
            const end = this.totalItems;
            const start = newRangeToFetch.start < end ? newRangeToFetch.start : end;
            newRangeToFetch = new IndexRange(start, end);
        }
        this.fetchedRegistry.clearFetched(newRangeToFetch);
    }
    onItemDeleted(itemIndex) {
        if (this.totalItems === 0) {
            return;
        }
        this.totalItems--;
        this.fetchedRegistry.removeFetched(itemIndex);
        const rangeToFetch = this.prefetchCount.getRangeToFetch(this.totalItems);
        this.logger.debug(`onItemDeleted new total: ${this.totalItems} fetch registry: ${this.fetchedRegistry.toString()}`);
        this.fetchedRegistry.decrementFetchedGreaterThen(itemIndex, rangeToFetch);
    }
    onItemAdded(itemIndex) {
        this.totalItems++;
        if (itemIndex > this.fetchedRegistry.rangeToFetch.end) {
            return;
        }
        const rangeToFetch = this.prefetchCount.getRangeToFetch(this.totalItems);
        this.fetchedRegistry.incrementFetchedGreaterThen(itemIndex - 1, rangeToFetch);
        this.logger.debug(`onItemAdded new total: ${this.totalItems} fetch registry: ${this.fetchedRegistry.toString()}`);
    }
    onItemsSwapped(a, b) {
        if (!this.fetchedRegistry.has(a) || !this.fetchedRegistry.has(b)) {
            this.fetchedRegistry.removeFetched(a);
            this.fetchedRegistry.removeFetched(b);
        }
    }
    onItemMoved(from, to) {
        if (!this.fetchedRegistry.has(from) || !this.fetchedRegistry.has(to)) {
            const rangeToFetch = this.fetchedRegistry.rangeToFetch;
            this.onItemDeleted(from);
            this.onItemAdded(to);
            this.fetchedRegistry.updateRangeToFetch(rangeToFetch);
        }
    }
    itemUpdated(index) {
        this.fetchedRegistry.removeFetched(index);
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class PrefetchRangeRatio {
    constructor(itemsOnScreen, fetchedRegistry, logger = dummyLogger) {
        this.itemsOnScreen = itemsOnScreen;
        this.fetchedRegistry = fetchedRegistry;
        this.logger = logger;
        this.TOLERANCE_RANGES = [
            {
                leftToleranceEdge: 180,
                rightToleranceEdge: 250,
                prefetchCountMaxRatioLeft: 0.5,
                prefetchCountMaxRatioRight: 1,
            },
            {
                leftToleranceEdge: 3000,
                rightToleranceEdge: 4000,
                prefetchCountMaxRatioLeft: 1,
                prefetchCountMaxRatioRight: 0.25,
            },
        ];
        this.ACTIVE_DEGREE = 0.5;
        this.VISIBLE_DEGREE = 2.5;
        this.meanPrefetchTime = 0;
        this.leftToleranceEdge = Number.MIN_VALUE;
        this.rightToleranceEdge = 250;
        this.oldRatio = 0;
        this._range = RatioRange.newEmpty();
        this._maxRatio = 0.5;
    }
    get range() {
        return this._range;
    }
    get maxRatio() {
        return this._maxRatio;
    }
    updateTiming(prefetchDuration) {
        // Check if not from file storage
        if (prefetchDuration > 20) {
            const weight = 0.95;
            this.meanPrefetchTime = this.meanPrefetchTime * weight + (1 - weight) * prefetchDuration;
        }
        this.logger.debug(`prefetchDifference prefetchDur=${prefetchDuration}, meanPrefetchDur=${this.meanPrefetchTime}`);
    }
    update(prefetchDuration) {
        this.updateTiming(prefetchDuration);
        if (this.meanPrefetchTime >= this.leftToleranceEdge && this.meanPrefetchTime <= this.rightToleranceEdge) {
            return 'ratio-not-changed';
        }
        let ratioChanged = false;
        if (this.meanPrefetchTime > this.rightToleranceEdge) {
            ratioChanged = this.updateOnGreaterThanRight();
        }
        else if (this.meanPrefetchTime < this.leftToleranceEdge) {
            ratioChanged = this.updateOnLessThanLeft();
        }
        return ratioChanged ? 'ratio-changed' : 'ratio-not-changed';
    }
    updateOnLessThanLeft() {
        let ratioChanged = false;
        for (let i = this.TOLERANCE_RANGES.length - 1; i >= 0; i--) {
            const limit = this.TOLERANCE_RANGES[i];
            if (this.meanPrefetchTime < limit.leftToleranceEdge) {
                ratioChanged = true;
                this._maxRatio = limit.prefetchCountMaxRatioLeft;
                this.rightToleranceEdge = limit.rightToleranceEdge;
                if (i !== 0) {
                    this.leftToleranceEdge = this.TOLERANCE_RANGES[i - 1].leftToleranceEdge;
                }
                else {
                    this.leftToleranceEdge = Number.MIN_VALUE;
                }
            }
        }
        return ratioChanged;
    }
    updateOnGreaterThanRight() {
        let ratioChanged = false;
        for (let i = 0; i < this.TOLERANCE_RANGES.length; i++) {
            const limit = this.TOLERANCE_RANGES[i];
            if (this.meanPrefetchTime > limit.rightToleranceEdge) {
                ratioChanged = true;
                this._maxRatio = limit.prefetchCountMaxRatioRight;
                this.leftToleranceEdge = limit.leftToleranceEdge;
                if (i + 1 !== this.TOLERANCE_RANGES.length) {
                    this.rightToleranceEdge = this.TOLERANCE_RANGES[i + 1].rightToleranceEdge;
                }
                else {
                    this.rightToleranceEdge = Number.MAX_VALUE;
                }
            }
        }
        return ratioChanged;
    }
    calculateRatio(prefetchCount, totalCount) {
        const visibleRange = this.itemsOnScreen.visibleRange;
        const start = Math.max(0, visibleRange.start - prefetchCount);
        const end = Math.min(totalCount, visibleRange.end + prefetchCount);
        const evaluatedPrefetchRange = new IndexRange(start, end);
        const completedActive = this.fetchedRegistry.getFetchedInRange(evaluatedPrefetchRange);
        const completedVisible = this.fetchedRegistry.getFetchedInRange(visibleRange);
        if (evaluatedPrefetchRange.length === 0 || visibleRange.length === 0) {
            return 0;
        }
        const ratio = Math.pow(completedActive / evaluatedPrefetchRange.length, this.ACTIVE_DEGREE) *
            Math.pow(completedVisible / visibleRange.length, this.VISIBLE_DEGREE);
        return Math.min(1, ratio);
    }
    updateRatioRange(ratio) {
        if (ratio > this.oldRatio) {
            this._range = new RatioRange(new RangeEdge(this.oldRatio, false), new RangeEdge(ratio, true));
        }
        else {
            this._range = new RatioRange(new RangeEdge(ratio, true), new RangeEdge(this.oldRatio, false));
        }
        this.oldRatio = ratio;
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class DefaultTimeProvider {
    getCurrent() {
        return Date.now();
    }
}
const dummyDataSource = {
    // istanbul ignore next
    prefetch: () => { },
    totalCount: () => {
        return 0;
    },
    // istanbul ignore next
    getData: () => {
        return undefined;
    },
    registerDataChangeListener: () => { },
    unregisterDataChangeListener: () => { },
};
const loadMoreSentinelItem = 1;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FetchingDriver {
    constructor(broker, fetches, rangeToFetchEvaluator, timeProvider, logger = dummyLogger, autostart = true, loadMoreCallback) {
        this.broker = broker;
        this.fetches = fetches;
        this.rangeToFetchEvaluator = rangeToFetchEvaluator;
        this.timeProvider = timeProvider;
        this.logger = logger;
        this.loadMoreCallback = loadMoreCallback;
        this.dataSourceObserver = new DataSourceObserver(this);
        this.enableLoadMoreCallbackCall = true;
        this.isPaused = !autostart;
        if (!this.isPaused) {
            this.broker.start();
        }
        this.dataSourceObserver.setDataSource(this.broker.dataSource);
    }
    get afterErrorDelay() {
        return 50;
    }
    batchUpdate(operations) {
        this.logger.info('batchUpdate called with ' + JSON.stringify(operations));
        try {
            this.batchUpdateInternal(operations);
        }
        catch (e) {
            // istanbul ignore next
            reportErrorInMethod(this.logger, 'batchUpdate', e);
            // istanbul ignore next
            throw e;
        }
    }
    batchUpdateInternal(operations) {
        operations.forEach((operation) => {
            switch (operation.kind) {
                case 'deleted':
                    this.itemsDeleted(operation.startIndex, operation.count);
                    break;
                case 'added':
                    this.itemsAdded(operation.startIndex, operation.count);
                    break;
                case 'updated':
                    this.itemUpdated(operation.index);
                    break;
                case 'reloaded':
                    this.collectionChanged(operation.totalCount);
                    break;
                case 'swapped':
                    this.itemsSwapped(operation.a, operation.b);
                    break;
                case 'moved':
                    this.itemMoved(operation.from, operation.to);
                    break;
            }
        });
        this.fetch();
    }
    collectionChanged(totalCount) {
        this.enableLoadMoreCallbackCall = true;
        this.rangeToFetchEvaluator.updateRangeToFetch({
            kind: 'collection-changed',
            totalCount: totalCount + (this.loadMoreCallback ? loadMoreSentinelItem : 0),
        });
    }
    itemUpdated(index) {
        this.rangeToFetchEvaluator.updateRangeToFetch({
            kind: 'item-updated',
            itemIndex: index,
        });
        this.fetches.deleteFetchByItem(index);
    }
    itemsDeleted(index, count) {
        for (let i = 0; i < count && index < this.dataSource.totalCount(); i++) {
            this.fetches.decrementAllIndexesGreaterThen(index);
            this.rangeToFetchEvaluator.updateRangeToFetch({ kind: 'item-removed', itemIndex: index });
        }
    }
    itemsAdded(index, count) {
        this.enableLoadMoreCallbackCall = true;
        for (let i = 0; i < count; i++) {
            this.fetches.incrementAllIndexesGreaterThen(index - 1);
            this.rangeToFetchEvaluator.updateRangeToFetch({ kind: 'item-added', itemIndex: index });
        }
    }
    itemsSwapped(a, b) {
        this.rangeToFetchEvaluator.updateRangeToFetch({ kind: 'items-swapped', indexA: a, indexB: b });
    }
    itemMoved(from, to) {
        this.rangeToFetchEvaluator.updateRangeToFetch({ kind: 'item-moved', from, to });
    }
    get dataSource() {
        return this.broker.dataSource;
    }
    setDataSource(ds = dummyDataSource) {
        this.logger.info(`setDataSource called with ${ds !== dummyDataSource ? 'a data source' : 'null or undefined'}`);
        try {
            this.setDataSourceInternal(ds);
        }
        catch (e) {
            // istanbul ignore next
            reportErrorInMethod(this.logger, 'setDataSource', e);
            // istanbul ignore next
            throw e;
        }
    }
    setDataSourceInternal(ds) {
        if (this.broker.dataSource === ds) {
            return;
        }
        this.dataSourceObserver.setDataSource(ds);
        if (this.broker.setDataSource) {
            this.broker.setDataSource(ds);
        }
    }
    stop() {
        this.logger.info('Stop called');
        try {
            this.stopInternal();
        }
        catch (e) {
            // istanbul ignore next
            reportErrorInMethod(this.logger, 'stop', e);
            // istanbul ignore next
            throw e;
        }
    }
    stopInternal() {
        if (this.isPaused)
            return;
        this.isPaused = true;
        this.cancel(this.fetches.getAllIndexes());
        this.broker.stop();
    }
    start() {
        this.logger.info('Start called');
        try {
            this.startInternal();
        }
        catch (e) {
            // istanbul ignore next
            reportErrorInMethod(this.logger, 'start', e);
            // istanbul ignore next
            throw e;
        }
    }
    startInternal() {
        if (!this.isPaused)
            return;
        this.isPaused = false;
        this.broker.start();
        this.fetch();
    }
    visibleAreaChanged(minVisible, maxVisible) {
        this.logger.info(`visibleAreaChanged min: ${minVisible} max: ${maxVisible}`);
        try {
            this.visibleAreaChangedInternal(minVisible, maxVisible);
        }
        catch (e) {
            reportErrorInMethod(this.logger, 'visibleAreaChanged', e);
            throw e;
        }
    }
    visibleAreaChangedInternal(minVisible, maxVisible) {
        if (this.broker.dataSource === dummyDataSource) {
            throw new Error('No data source');
        }
        const oldRangeToPrefetch = this.rangeToFetchEvaluator.rangeToFetch;
        this.rangeToFetchEvaluator.updateRangeToFetch({ kind: 'visible-area-changed', minVisible, maxVisible });
        this.fetch();
        if (this.rangeToFetchEvaluator.rangeToFetch.length <= oldRangeToPrefetch.length) {
            const toCancel = oldRangeToPrefetch.subtract(this.rangeToFetchEvaluator.rangeToFetch).toSet();
            this.cancel(toCancel);
        }
    }
    fetch() {
        if (this.isPaused) {
            this.logger.debug('Prefetcher is paused. Do nothing.');
            return;
        }
        const toFetch = this.rangeToFetchEvaluator.getItemsToFetch();
        let totalCount = this.dataSource.totalCount();
        this.logger.debug(`Total count on fetch ${totalCount} registry state ${this.rangeToFetchEvaluator.fetchedRegistry.toString()} evl count: ${this.rangeToFetchEvaluator.totalItems}`);
        toFetch.forEach((itemIndex) => {
            if (itemIndex >= totalCount) {
                this.logger.debug('A');
            }
            if (this.fetches.isFetchingItem(itemIndex)) {
                if (itemIndex >= totalCount) {
                    this.logger.warn('Total is fetching');
                }
                return;
            }
            if (this.rangeToFetchEvaluator.isItemFetched(itemIndex)) {
                if (itemIndex >= totalCount) {
                    this.logger.warn('Total is fetched');
                }
                return;
            }
            // if (this.fetches.isFetchingItem(itemIndex) || this.rangeToFetchEvaluator.isItemFetched(itemIndex)) return;
            if (itemIndex >= totalCount) {
                this.logger.debug('B');
            }
            if (this.loadMoreCallback && itemIndex >= totalCount) {
                this.logger.debug('Good to load more');
                if (this.enableLoadMoreCallbackCall) {
                    this.enableLoadMoreCallbackCall = false;
                    this.logger.info('Request to load more. Current collection size is ' + totalCount);
                    reportErrorInMethod(this.logger, 'request-to-load-more', new Error());
                    this.loadMoreCallback();
                    totalCount = this.dataSource.totalCount();
                }
                return;
            }
            const prefetchStart = this.timeProvider.getCurrent();
            const fetchId = this.fetches.registerFetch(itemIndex);
            //  console.log('to prefetch ' + itemIndex);
            this.logger.info('to fetch ' + itemIndex);
            if ((totalCount - itemIndex) === 10) {
                reportErrorInMethod(this.logger, 'dangerous-diff', new Error());
            }
            try {
                const prefetchResponse = this.broker.fetch(itemIndex);
                if (prefetchResponse instanceof Promise) {
                    prefetchResponse
                        .then(() => this.fetchedCallback(fetchId, prefetchStart))
                        .catch((e) => {
                        this.errorOnFetchCallback(fetchId, e);
                    });
                }
                else {
                    this.fetchedCallback(fetchId, prefetchStart);
                }
            }
            catch (e) {
                this.errorOnFetchCallback(fetchId, e);
            }
        });
    }
    fetchedCallback(fetchId, prefetchStart) {
        const itemIndex = this.fetches.getItem(fetchId);
        this.fetches.deleteFetch(fetchId);
        if (itemIndex === undefined)
            return;
        // console.log('prefetchedCallback fid: ' + fetchId + ' index: ' + itemIndex)
        this.rangeToFetchEvaluator.updateRangeToFetch({
            kind: 'item-fetched',
            itemIndex,
            fetchDuration: this.timeProvider.getCurrent() - prefetchStart,
        });
        this.fetch();
    }
    errorOnFetchCallback(fetchId, error) {
        const itemIndex = this.fetches.getItem(fetchId);
        if (itemIndex !== undefined) {
            reportError(this.logger, `failed to fetch item at ${itemIndex}`, error);
        }
        this.fetches.deleteFetch(fetchId);
        setTimeout(() => {
            this.fetch();
        }, this.afterErrorDelay);
    }
    cancel(toCancel) {
        toCancel.forEach((itemIndex) => {
            if (!this.fetches.isFetchingItem(itemIndex))
                return;
            this.fetches.deleteFetchByItem(itemIndex);
            if (this.broker.cancel) {
                this.logger.info('to cancel ' + itemIndex);
                this.broker.cancel(itemIndex);
            }
        });
    }
}
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const dummyLogger = {
    debug: (message) => { },
    info: (message) => { },
    warn: (message) => { },
};
function reportErrorInMethod(logger, methodName, e) {
    logger.warn(`Error in ${methodName}: ${e}\n${e.stack}`);
}
function reportError(logger, message, e) {
    logger.warn(`${message} ${e instanceof Error ? e : JSON.stringify(e)}`);
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
class IndexRange {
    constructor(start, end) {
        this.start = start;
        this.end = end;
        if (this.start > this.end) {
            throw new Error('Invalid range');
        }
    }
    get length() {
        return this.end - this.start;
    }
    toSet(target) {
        const set = target ?? new Set();
        for (let i = this.start; i < this.end; ++i) {
            set.add(i);
        }
        return set;
    }
    contains(value) {
        if (typeof value === 'object') {
            return this.start <= value.start && value.end <= this.end;
        }
        else {
            return this.start <= value && value < this.end;
        }
    }
    subtract(other) {
        const result = new IndexRangeArray();
        if (other.start > this.start) {
            result.push(new IndexRange(this.start, Math.min(this.end, other.start)));
        }
        if (other.end < this.end) {
            result.push(new IndexRange(Math.max(other.end, this.start), this.end));
        }
        return result;
    }
    // Expand the range to contain another.
    // When `this` and `other` intersect, this is a union.
    expandedWith(other) {
        return new IndexRange(Math.min(this.start, other.start), Math.max(this.end, other.end));
    }
    forEachIndex(callback) {
        for (let i = this.start; i < this.end; ++i) {
            callback(i);
        }
    }
}
class IndexRangeArray extends Array {
    forEachIndex(callback) {
        this.forEach((range) => {
            range.forEachIndex(callback);
        });
    }
    toSet() {
        const set = new Set();
        this.forEach((range) => {
            range.toSet(set);
        });
        return set;
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
class RangeEdge {
    constructor(value, inclusive) {
        this.value = value;
        this.inclusive = inclusive;
    }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class RatioRange {
    constructor(start, end) {
        this.start = start;
        this.end = end;
        if (this.start.value > this.end.value) {
            throw new Error(`RatioRange: ${this.start.value} > ${this.end.value}`);
        }
    }
    static newEmpty() {
        return new RatioRange(new RangeEdge(0, false), new RangeEdge(0, false));
    }
    contains(point) {
        if (point === this.start.value) {
            return this.start.inclusive;
        }
        if (point === this.end.value) {
            return this.end.inclusive;
        }
        return this.start.value < point && point < this.end.value;
    }
    toString() {
        return `${this.start.inclusive ? '[' : '('}${this.start.value}, ${this.end.value}${this.end.inclusive ? ']' : ')'}`;
    }
}
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// istanbul ignore next
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function assertNever(_) {
    throw _ + 'assertNever';
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FetchIdProvider {
    static get id() {
        return FetchIdProvider.fetchId++;
    }
}
FetchIdProvider.fetchId = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class MainThreadFetchAgentBroker {
    constructor(dataSource, fetchAgent, logger = dummyLogger) {
        this.dataSource = dataSource;
        this.fetchAgent = fetchAgent;
        this.logger = logger;
        this.indexToFetch = new Map();
    }
    async fetch(index) {
        const item = this.dataSource.getData(index);
        if (!item || !item.hasToFetch()) {
            return;
        }
        const payload = item.createFetchInfo();
        const fetchId = FetchIdProvider.id;
        this.indexToFetch.set(index, fetchId);
        try {
            this.logger.debug(`Fetch ${fetchId} at index ${index}`);
            const result = await this.fetchAgent.fetch(fetchId, payload);
            const r = item.onFetchDone(result);
            if (r instanceof Promise) {
                await r;
            }
        }
        catch (e) {
            if (!(e instanceof CanceledError)) {
                item.onFetchFail(e);
                throw e;
            }
        }
        finally {
            // console.log('fetchDone ' + index);
            this.indexToFetch.delete(index);
        }
    }
    cancel(index) {
        // console.log('cancelItem ' + index)
        if (!this.fetchAgent.cancel) {
            return;
        }
        const fetchId = this.indexToFetch.get(index);
        if (fetchId !== undefined) {
            this.fetchAgent.cancel(fetchId);
        }
    }
    start() { }
    stop() { }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class WorkerThreadFetchAgentBroker {
    constructor(prefetcherId, dataSource, workerThreadProxy, logger = dummyLogger) {
        this.prefetcherId = prefetcherId;
        this.dataSource = dataSource;
        this.workerThreadProxy = workerThreadProxy;
        this.logger = logger;
        this.indexToFetch = new Map();
        this.messageToFetch = new Map();
    }
    async fetch(index) {
        const item = this.dataSource.getData(index);
        if (!item || !item.hasToFetch()) {
            return;
        }
        let fetch;
        const promise = new Promise((resolve, reject) => {
            fetch = {
                resolve: resolve,
                reject: reject,
                item: item,
                index,
            };
        });
        const fetchId = FetchIdProvider.id;
        const message = new FetchItemMessage(this.prefetcherId, fetchId, item.createFetchInfo());
        this.messageToFetch.set(fetchId, fetch);
        this.indexToFetch.set(index, fetchId);
        this.workerThreadProxy.send(message);
        return promise;
    }
    cancel(index) {
        const fetchId = this.indexToFetch.get(index);
        if (fetchId !== undefined) {
            this.indexToFetch.delete(index);
            this.workerThreadProxy.send(new CancelFetchMessage(this.prefetcherId, fetchId));
            const fetchMessage = this.messageToFetch.get(fetchId);
            this.messageToFetch.delete(fetchId);
            fetchMessage.resolve();
        }
    }
    start() {
        this.workerThreadProxy.registerListener(this);
    }
    stop() {
        this.workerThreadProxy.unregisterListener(this);
    }
    onMessageFromWorker(message) {
        const fetchMessage = this.messageToFetch.get(message.fetchId);
        if (!fetchMessage) {
            return;
        }
        // this.logger.debug(`onMessageFromWorker ${message}`);
        const resultMessage = message;
        this.messageToFetch.delete(resultMessage.fetchId);
        this.indexToFetch.delete(fetchMessage.index);
        switch (resultMessage.kind) {
            case 'done':
                try {
                    const r = fetchMessage.item.onFetchDone(resultMessage.payload);
                    if (r instanceof Promise) {
                        r.then(() => {
                            fetchMessage.resolve();
                        }).catch((err) => {
                            fetchMessage.reject(err);
                        });
                    }
                    else {
                        fetchMessage.resolve();
                    }
                }
                catch (err) {
                    fetchMessage.reject(err);
                }
                break;
            case 'fail':
                try {
                    const r = fetchMessage.item.onFetchFail(resultMessage.error);
                    if (r instanceof Promise) {
                        r.finally(() => {
                            fetchMessage.reject(resultMessage.error);
                        });
                    }
                }
                catch (e) {
                    reportError(this.logger, 'Error in item.onFetchFail', e);
                }
                fetchMessage.reject(resultMessage.error);
                break;
        }
    }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class WorkerThreadProxy {
    constructor(workerThread, logger = dummyLogger) {
        this.workerThread = workerThread;
        this.listeners = new Set();
        const prevOnMessage = workerThread.onmessage;
        workerThread.onmessage = (e) => {
            logger.debug('Receive ' + JSON.stringify(e));
            if (e.data &&
                e.data.uuid === prefetcherMessagesUUID &&
                'fetchId' in e.data &&
                typeof e.data.fetchId === 'number' &&
                'kind' in e.data &&
                (e.data.kind === 'done' || e.data.kind === 'fail')) {
                logger.debug(`Notify ${this.listeners.size} listeners about ` + JSON.stringify(e));
                const message = e.data;
                this.listeners.forEach((listener) => {
                    listener.onMessageFromWorker(message);
                });
            }
            if (prevOnMessage) {
                prevOnMessage(e);
            }
        };
    }
    registerListener(listener) {
        this.listeners.add(listener);
    }
    unregisterListener(listener) {
        this.listeners.delete(listener);
    }
    send(message) {
        this.workerThread.postMessage(message);
    }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class WorkerPortProxy {
    constructor(senderIdOrMatchCallback, workerPort, agent, logger = dummyLogger) {
        this.workerPort = workerPort;
        this.agent = agent;
        this.logger = logger;
        const prevHandler = workerPort.onmessage;
        workerPort.onmessage = (e) => {
            if (e.data &&
                e.data.uuid === prefetcherMessagesUUID &&
                (typeof senderIdOrMatchCallback === 'function'
                    ? senderIdOrMatchCallback(e.data.senderId)
                    : e.data.senderId === senderIdOrMatchCallback) &&
                'fetchId' in e.data &&
                typeof e.data.fetchId === 'number' &&
                'kind' in e.data &&
                (e.data.kind === 'fetch' || e.data.kind === 'cancel')) {
                this.logger.debug('Receive ' + JSON.stringify(e));
                const message = e.data;
                switch (message.kind) {
                    case 'fetch':
                        this.fetch(message.fetchId, message.payload);
                        break;
                    case 'cancel':
                        this.cancel(message.fetchId);
                        break;
                }
            }
            if (prevHandler) {
                prevHandler(e);
            }
        };
    }
    async fetch(fetchId, payload) {
        try {
            const result = await this.agent.fetch(fetchId, payload);
            this.notifyOnFetchDone(fetchId, result);
        }
        catch (e) {
            reportError(this.logger, 'Error on fetch', e);
            this.notifyOnFetchError(fetchId, e);
        }
    }
    notifyOnFetchDone(fetchId, result) {
        const answer = {
            uuid: prefetcherMessagesUUID,
            fetchId,
            kind: 'done',
            payload: result,
        };
        this.workerPort.postMessage(answer);
    }
    notifyOnFetchError(fetchId, error) {
        if (!(error instanceof CanceledError)) {
            const answer = {
                uuid: prefetcherMessagesUUID,
                fetchId,
                kind: 'fail',
                error,
            };
            this.workerPort.postMessage(answer);
        }
    }
    cancel(fetchId) {
        try {
            this.agent.cancel(fetchId);
        }
        catch (e) {
            // istanbul ignore next
            reportError(this.logger, 'Error on cancel', e);
        }
    }
}
const prefetcherMessagesUUID = 'ab0cf56f-8dcb-421b-a52d-55d36ed97bd3';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FetchItemMessage {
    constructor(senderId, fetchId, payload) {
        this.senderId = senderId;
        this.fetchId = fetchId;
        this.payload = payload;
        this.kind = 'fetch';
        this.uuid = prefetcherMessagesUUID;
    }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class CancelFetchMessage {
    constructor(senderId, fetchId) {
        this.senderId = senderId;
        this.fetchId = fetchId;
        this.kind = 'cancel';
        this.uuid = prefetcherMessagesUUID;
    }
}

module.exports = {
  IndexRange,
  IndexRangeArray,
  RangeEdge,
  RatioRange,
  BasicPrefetcher,
  FetchingRegistry,
  FetchedRegistry,
  ItemsOnScreenProvider,
  PrefetchCount,
  FetchingDriver,
  RangeToFetchProvider,
  PrefetchRangeRatio,
  CancelFetchMessage,
  BrokerBasedPrefetcher,
  MainThreadFetchAgentBroker,
  WorkerThreadFetchAgentBroker,
  WorkerPortProxy,
  WorkerThreadProxy,
  CanceledError,
  PrefetcherBuilder,
  WorkerLinker,
  DataSourceBroker,
  createPrefetcher,
  linkPrefetcher,
  prefetcherMessagesUUID,
  FetchItemMessage,
  CancelFetchMessage
};
